package skymeta

import (
	"sync"

	"gitee.com/simonxie979/skymeta/protocol"

	"github.com/gogo/protobuf/proto"
)

var (
	center *serviceCenter
)

type serviceCenter struct {
	mutex          sync.RWMutex
	localService   map[uint64]*container            // 本地节点服务列表
	sessionService map[uint64]map[uint64]*container // 其它节点服务列表 sessionID --> serviceID --> container
	allService     map[uint64]*container            // 服务列表 serviceID --> container
}

// RemoteServiceLaunch The remote node starts a service and notifies to add it.
func (sc *serviceCenter) RemoteServiceLaunch(sessionID uint64, srv *container) {
	sc.mutex.Lock()
	defer sc.mutex.Unlock()

	if _, exist := sc.sessionService[sessionID]; !exist {
		sc.sessionService[sessionID] = make(map[uint64]*container)
	}

	sc.sessionService[sessionID][srv.GetServiceID()] = srv
	sc.allService[srv.GetServiceID()] = srv
}

// RemoteServiceShutdown The remote node shutdown a service and notifies to remove it.
func (sc *serviceCenter) RemoteServiceShutdown(sessionID, serviceID uint64) {
	sc.mutex.Lock()
	defer sc.mutex.Unlock()

	delete(sc.allService, serviceID)
	if _, exist := sc.sessionService[sessionID]; !exist {
		delete(sc.sessionService[sessionID], serviceID)
	}
}

// RemoteOffline The remote node disconnect, remove all services from this remote node.
func (sc *serviceCenter) RemoteOffline(sessionID uint64) {
	sc.mutex.Lock()
	defer sc.mutex.Unlock()

	sessionSrvs, exist := sc.sessionService[sessionID]
	if !exist {
		return
	}

	delete(sc.sessionService, sessionID)
	for serviceID := range sessionSrvs {
		delete(sc.allService, serviceID)
	}
}

// RemoteServiceList The remote node notifies to add all services from it.
func (sc *serviceCenter) RemoteServiceList(sessionID uint64, list []*container) {
	sc.mutex.Lock()
	defer sc.mutex.Unlock()

	if sessionSrvs, exist := sc.sessionService[sessionID]; exist {
		delete(sc.sessionService, sessionID)
		for serviceID := range sessionSrvs {
			delete(sc.allService, serviceID)
		}
	}

	sc.sessionService[sessionID] = make(map[uint64]*container)
	for _, v := range list {
		sc.sessionService[sessionID][v.GetServiceID()] = v
		sc.allService[v.GetServiceID()] = v
	}
}

// LocalServiceLaunch Add a local service to map.
func (sc *serviceCenter) LocalServiceLaunch(srv *container) {
	sc.mutex.Lock()
	defer sc.mutex.Unlock()

	sc.localService[srv.GetServiceID()] = srv
	sc.allService[srv.GetServiceID()] = srv
}

// LocalServiceShutdown Remote a local service from map.
func (sc *serviceCenter) LocalServiceShutdown(serviceID uint64) *container {
	sc.mutex.Lock()
	defer sc.mutex.Unlock()

	srv := sc.localService[serviceID]
	if srv != nil {
		delete(sc.localService, serviceID)
		delete(sc.allService, serviceID)
	}

	return srv
}

// GetService Gets service by service id.
// If Service not found will return nil.
func (sc *serviceCenter) GetService(serviceID uint64) *container {
	sc.mutex.RLock()
	defer sc.mutex.RUnlock()

	return sc.allService[serviceID]
}

// GetAllService Get all service from map.
func (sc *serviceCenter) GetAllService() (list []*container) {
	sc.mutex.RLock()
	defer sc.mutex.RUnlock()

	for _, v := range sc.allService {
		list = append(list, v)
	}

	return
}

// GetLocalServices Get all local services.
func (sc *serviceCenter) GetLocalServices() (list []*container) {
	sc.mutex.RLock()
	defer sc.mutex.RUnlock()

	for _, v := range sc.localService {
		list = append(list, v)
	}

	return
}

func init_ServiceCenter() {
	center = new(serviceCenter)
	center.localService = make(map[uint64]*container)
	center.sessionService = make(map[uint64]map[uint64]*container)
	center.allService = make(map[uint64]*container)
}

func exit_ServiceCenter() {
	center.localService = make(map[uint64]*container)
	center.sessionService = make(map[uint64]map[uint64]*container)

	for _, srv := range center.allService {
		srv.Shutdown()
	}
}

// NewService Launch a local service, and notify to cluster.
func NewService(instance IService) error {
	container, err := newLocalContainer(instance)
	if err != nil {
		return err
	}

	ntc := &protocol.ServiceLaunch{
		Info: &protocol.ServiceInfo{
			ID:   instance.GetServiceID(),
			Name: instance.GetServiceName(),
		},
	}
	data, err := proto.Marshal(ntc)
	if err != nil {
		logger.Errorf("ServiceCenter", "NewService %s marshal ServiceLaunch notify failure. err: %v", instance.GetServiceName(), err)
		return err
	}

	ssMsg := &protocol.SSMessage{
		Type:        protocol.MessageType_Frame,
		Source:      0,
		Destination: 0,
		Sequence:    0,
		Name:        proto.MessageName(ntc),
		Body:        data,
	}

	center.LocalServiceLaunch(container)

	node.BordcastMsg(ssMsg)

	logger.Debugf("ServiceCenter", "new service %s --> %X success.", instance.GetServiceName(), instance.GetServiceID())

	return nil
}

// KillService Shutdwon a local service, and notify to cluster.
func KillService(serviceID uint64) {
	container := center.LocalServiceShutdown(serviceID)
	if container == nil {
		return
	}
	defer container.Shutdown()

	ntc := &protocol.ServiceShutdown{
		Info: &protocol.ServiceInfo{
			ID:   container.GetServiceID(),
			Name: container.GetServiceName(),
		},
	}
	data, err := proto.Marshal(ntc)
	if err != nil {
		logger.Errorf("ServiceCenter", "NewService %s marshal ServiceShutdown notify failure. err: %v", container.GetServiceName(), err)
		return
	}

	ssMsg := &protocol.SSMessage{
		Type:        protocol.MessageType_Frame,
		Source:      0,
		Destination: 0,
		Sequence:    0,
		Name:        proto.MessageName(ntc),
		Body:        data,
	}

	node.BordcastMsg(ssMsg)
}

// ListService Divided all service to a map according to service name and return the map.
func ListService() (res map[string][]uint64) {
	res = make(map[string][]uint64)

	for _, srv := range center.GetAllService() {
		srvName := srv.GetServiceName()
		if _, exist := res[srvName]; !exist {
			res[srvName] = make([]uint64, 0)
		}

		res[srvName] = append(res[srvName], srv.GetServiceID())
	}

	return
}

// QueryService Match service name of everyone service with 'srvName',
// put the service id of matched service to a slice, and return the slice.
func QueryService(srvName string) (list []uint64) {
	for _, srv := range center.GetAllService() {
		if srv.GetServiceName() == srvName {
			list = append(list, srv.GetServiceID())
		}
	}

	return
}
